/*
 * Crypt.java
 *
 * Crypt or proliferative unit.
 */

package sim;
import java.util.ArrayList;
//import java.awt.Color;
import lib.KnuthRandom;
import lib.Probability;

abstract public class Crypt {

    /**
     * These static variables, /one value for all crypts created/, hold the index of the last rate class, which is reserved for dead crypts   
     */    
    public static int divisionRateClassDeadCryptsClass;
    public static int deathRateClassDeadCryptsClass;
    public static int neutralMutationRateClassDeadCryptsClass;
    public static int selectiveMutationRateClassDeadCryptsClass;    

    /**
     * Each crypt stores the index at which it is positioned in the array that holds the crypts
     */    
    public int divisionClassIndex;
    public int deathClassIndex;
    public int neutralMutationClassIndex;
    public int selectiveMutationClassIndex;

    /** 
     * Each crypt stores the class to which it belongs. E.g. division class number 3
     * are created based on the number of different loci which confer reproduction advantage
     */
    public int divisionClass;
    public int deathClass;
    public int neutralMutationClass;
    public int selectiveMutationClass;
    /*
    public int getDivisionClass(){
	return divisionClass;
    }
    public int getDeathClass(){
	return deathClass;
    }
    public int getNeutralMutationClass(){
	return neutralMutationClass;
    }
    public int getSelectiveMutationClass(){
	return selectiveMutationClass;
    }

    public void setDivisionClass(int c){
	divisionClass = c;
    }
    public void setDeathClass(int c){
	deathClass = c;
    }
    public void setNeutralMutationClass(int c){
	neutralMutationClass = c;
    }
    public void setSelectiveMutationClass(int c){
	selectiveMutationClass = c;
    }

    public int getDivisionClassIndex(){
	return divisionClassIndex;
    }
    public int getDeathClassIndex(){
	return deathClassIndex;
    }
    public int getMutationClassNeutralIndex(){
	return mutationClassNeutralIndex;
    }
    public int getMutationClassSelectiveIndex(){
	return mutationClassSelectiveIndex;
    }

    public void setDivisionClassIndex(int c){
	divisionClassIndex = c;
    }
    public void setDeathClassIndex(int c){
	deathClassIndex = c;
    }
    public void setMutationClassNeutralIndex(int c){
	mutationClassNeutralIndex = c;
    }
    public void setMutationClassSelectiveIndex(int c){
	mutationClassSelectiveIndex = c;
    }
    */
    final double EPSILON = 0.0000000000000001; // comparing doubles

    protected static int _dispersalRadius = 300;
    protected static int _neutralLociSize;
    protected static int _selectiveLociSize;
    protected static byte[] _deadNeighborsIndices;

    // For all crypts, set the probability
    protected static double _cryptDivisionProbability;
    public void setCryptDivisionProbability(double x) { _cryptDivisionProbability=x; }

    // All crypt instances have one static cloneCounter that counts the number of clones
    // and every time a new clone is generated, cloneCounter is increased by 1
    // Thus, the first mutant clone will have a clone_id = 1, the second clone will have clone_id = 2
    // and so on.
    // Also, all crypts have one static clonePhylogeny that keeps track of the phylogeny of clones
    // every time a crypt dies, mutates or divides, we update the clone table and make
    // sure we keep all clone extinction and clone generation times
    // At "callback" events, we can count frequency of clones or we can calculate them on the fly
    // Every Crypt object has its own current _cloneId specifying which 
    protected int _cloneId;


    // Data Members
    protected static PGrid _grid;      // grid that manages the geometry of crypts
    protected int _nVertexIndex;// position in Grid class
    protected ArrayList<Crypt> _neighbors; // list of neighboring crypts

    private TissueEnvironmentSettings.CryptMode _mode;
    
    // Constructor
    public Crypt() { 
	_neighbors = new ArrayList<Crypt>();
	// Hexagonal grid, 6 neighbors.
	_deadNeighborsIndices = new byte[6];
	//_color = COLOR_ALIVE_WT;
	// Make clone_Id wild-type = 0
	_cloneId = 0;		
    }

    // Copy Constructor
    public Crypt( Crypt rhs ) {
	_nVertexIndex = rhs._nVertexIndex;
	_grid = rhs._grid;
    }

    public void setParams(TissueEnvironmentSettings params) {
	setCryptMode(params.getCryptMode());
	_neutralLociSize = params.getMSLociSize();
	_selectiveLociSize = params.getLociCount()-params.getMSLociSize();
	// Set division probability for each crypt, though probability is made static, for all crypts
	setCryptDivisionProbability(params.getCryptDivisionProbability());
	_dispersalRadius = params.getDispersalRadius();
    }

    public void setCryptMode(TissueEnvironmentSettings.CryptMode x) { _mode = x; }
    public TissueEnvironmentSettings.CryptMode getCryptMode() { return _mode; }
	
    public void addNeighbor(Crypt c) { _neighbors.add(c); }
    public void setNeighbor(int i, Crypt c) {_neighbors.set(i, c);}
    public ArrayList<Crypt> getNeighbors() { return _neighbors; }
    public void setGrid(PGrid g) { _grid = g; }
    public void setIndex(int n) { _nVertexIndex = n; }
    public int getIndex() { return _nVertexIndex; }
    public double getX() { return _grid.getX(_nVertexIndex); }
    public double getY() { return _grid.getY(_nVertexIndex); }

    public void setCloneId(int n) { _cloneId = n; }	
    public int getCloneId() { return _cloneId; }	


    // A dead crypt has a cloneId of -1
    public boolean isAlive() { return (_cloneId != -1); }


    // Computes the distance from this crypt to the specified location on the tissue
    public double distanceTo( double x, double y )
    {
	return Math.hypot( (getX()-x), (getY()-y) );
    }

    // Returns a list of the ordinal index of all neighbors with the specified state of being alive (true/false)
    protected byte GetDeadCount()
    {
	byte res = 0;
	for(byte i = 0; i < _neighbors.size(); i++ ) {
	    if(!((Crypt)_neighbors.get(i)).isAlive()){
		// Save index of dead neighbor 
		_deadNeighborsIndices[res]=i;
		res++;
	    }
	}		
	return res;
    }

    // Get a random dead neighbor, make sure GetDeadCount is called before this
    // function so that the array _deadNeighborsIndices is initialized properly
    // and its size "deadCount" is passed to this function
    protected int GetRandomDeadNeighborIndex(Scheduler s, byte deadCount )
    {
	int iRandNeighbor = s.GetRNG().randomInt(deadCount);
	return _deadNeighborsIndices[iRandNeighbor];
    }

    // Get a random neighbor
    // Note: some corner crypts have less than 6 neighbors
    protected int GetRandomNeighborIndex(Scheduler s)
    {
	return s.GetRNG().randomInt(_neighbors.size());
    }
    
    protected ArrayList<Integer> GetLivingNeighborIndices()
    {
	ArrayList<Integer> neighborIndices = new ArrayList<Integer>();
	for( int i = 0; i < _neighbors.size(); i++ ) {
	    Crypt neighbor = _neighbors.get(i);
	    if (neighbor.isAlive())
		neighborIndices.add(i);
	}
		
	return neighborIndices;
    }

    protected ArrayList<Integer> GetDeadNeighborIndices()
    {
	ArrayList<Integer> neighborIndices = new ArrayList<Integer>();
	for( int i = 0; i < _neighbors.size(); i++ ) {
	    Crypt neighbor = _neighbors.get(i);
	    if (!neighbor.isAlive())
		neighborIndices.add(i);
	}
		
	return neighborIndices;
    }
    

    abstract public void die(Scheduler s) throws Exception;
    abstract public void divide(Scheduler s) throws Exception;
    abstract public void mutateNeutral(Scheduler s) throws Exception;
    abstract public void mutateSelective(Scheduler s) throws Exception;

}
